Skip to content

[Proposal] Add agentic ID support#1027

Open
Avery-Dunn wants to merge 3 commits intoavdunn/fic-supportfrom
avdunn/agent-id-support
Open

[Proposal] Add agentic ID support#1027
Avery-Dunn wants to merge 3 commits intoavdunn/fic-supportfrom
avdunn/agent-id-support

Conversation

@Avery-Dunn
Copy link
Copy Markdown
Contributor

@Avery-Dunn Avery-Dunn commented Apr 15, 2026

This PR adds the high-level acquireTokenForAgent API to MSAL Java, enabling the full three-leg agent identity token acquisition flow. This is the developer-facing API that orchestrates the FMI (PR 1) and UserFIC (PR 2) building blocks internally, eliminating the need for developers to manage multiple CCA instances, assertion callbacks, and token caches manually.

The API matches the AcquireTokenForAgent API on MSAL .NET's avdunn/agent-identity-apis branch (PR #5883).

What's included

New public APIs:

  • IConfidentialClientApplication.acquireTokenForAgent(AcquireTokenForAgentParameters) — acquires a user-scoped token for agent scenarios via the three-leg FMI/FIC flow
  • AcquireTokenForAgentParameters — parameter object with builder pattern, supporting:
    • .builder(scopes, agentIdentity) — required: target scopes and agent identity
    • .forceRefresh(boolean) — bypass the user token cache
    • .tenant(String) — override tenant for all inner calls
    • .claims(ClaimsRequest) — claims challenge support, propagated to inner calls
    • .extraHttpHeaders(Map) / .extraQueryParameters(Map) — extensibility, propagated to inner calls
  • AgentIdentity — identifies the agent application and (optionally) the target user:
    • new AgentIdentity(agentApplicationId, userObjectId) — identify user by OID (recommended)
    • AgentIdentity.withUsername(agentApplicationId, username) — identify user by UPN
    • AgentIdentity.appOnly(agentApplicationId) — app-only flow (Legs 1-2 only, no user token)

Three-leg orchestration (AcquireTokenForAgentSupplier):

The supplier orchestrates the full flow internally:

  1. Silent check — searches the Agent CCA's user token cache for a cached token matching the user identity (by OID or UPN). If found and not expired, returns immediately. Skipped when forceRefresh=true.
  2. Leg 1 (FMI credential) — acquires a federated managed identity credential from the Blueprint CCA via AcquireTokenForClient with fmiPath=agentAppId. Cached in the Blueprint's app token cache.
  3. Leg 2 (assertion token) — acquires an assertion token from the Agent CCA via AcquireTokenForClient with credentialFmiPath=agentAppId (cache-key-only, not sent in HTTP body). The Agent CCA's assertion callback triggers Leg 1. Cached with extended cache key hash for isolation.
  4. Leg 3 (user token) — exchanges the assertion for a user-scoped token via AcquireTokenByUserFederatedIdentityCredential. Always fetches from network (cache was already checked in the silent check above). Result is stored in the Agent CCA's user token cache.

Internal CCA management:

  • ConfidentialClientApplication.agentCcaCache — a ConcurrentHashMap that stores internal Agent CCA instances, keyed by "agent_" + agentAppId
  • Agent CCAs are created lazily on first use and reused across calls, preserving their token caches
  • Blueprint configuration (HTTP client, authority, logging, instance discovery, telemetry) is propagated to Agent CCAs

Cache key isolation (credential_fmi_path):

  • ClientCredentialParameters.credentialFmiPath(String) — new cache-key-only parameter (does NOT send anything in the HTTP body, unlike fmiPath)
  • Leg 2's assertion token is stored with an extCacheKeyHash computed from credential_fmi_path=agentAppId, preventing collisions with user tokens that share the same scope (api://AzureADTokenExchange/.default)
  • fmiPath and credentialFmiPath are mutually exclusive (validated at build time)
  • TokenCache.computeExtCacheKeyHashForRequest() extended to handle both fmi_path and credential_fmi_path components
  • AcquireTokenByClientCredentialSupplier propagates credentialFmiPath hash to silent lookups
  • This matches .NET's WithFmiPathForClientAssertion(agentAppId) behavior

Parameter propagation:

Outer AcquireTokenForAgentParameters are propagated to all inner token calls:

  • claims → Legs 1, 2, 3, and silent check (ensures claims challenges cause cache bypass)
  • tenant → Legs 1, 2, 3, and silent check
  • extraQueryParameters → Legs 1, 2, 3, and silent check
  • extraHttpHeaders → Legs 1, 2, 3, and silent check

This matches .NET's PropagateOuterRequestParameters pattern.

Tests:

  • AgentIdentityTest.java — 15 unit tests covering:
    • Constructor by OID (properties, null/empty validation)
    • Factory by UPN (properties, null/empty/blank validation)
    • Factory for app-only (properties, null/empty validation)
    • hasUserIdentifier() behavior for all three variants
  • AcquireTokenForAgentTest.java — 18 unit tests covering:
    • Core two-user flow with cache reuse (matches .NET's TwoUpns test)
    • App-only flow (Legs 1-2 only)
    • App-only cache hit on second call
    • ForceRefresh bypasses user cache but not Leg 1/2 caches
    • User identification by OID
    • Agent CCA caching (same agent ID reuses CCA instance)
    • Leg 1 sends fmi_path in request body
    • Leg 3 sends grant_type=user_fic
    • Input validation (null parameters, null scopes, null agent identity)
    • Two Blueprint CCAs do not share agent CCA caches
    • UPN → OID shared cache (same user found by either identifier)
    • Parameter propagation (tenant, extra query params, claims)
    • Comprehensive cache behavior (3 users, token counts, FMI vs non-FMI isolation, homeAccountId isolation)
    • Leg 2 cache key isolation via credential_fmi_path (verifies exact hash, no HTTP body leak, correct assertion used by subsequent users)
  • AgenticIT.java — 4 integration tests covering the full agent identity flow with real certificates

Alignment with MSAL .NET

This implementation matches the agent identity behavior on MSAL .NET's avdunn/agent-identity-apis branch. Key equivalences:

.NET Java
AcquireTokenForAgent(scopes, agentIdentity) acquireTokenForAgent(AcquireTokenForAgentParameters.builder(scopes, agentIdentity).build())
AgentIdentity(agentAppId, userObjectId) new AgentIdentity(agentAppId, userObjectId)
AgentIdentity.WithUsername(agentAppId, username) AgentIdentity.withUsername(agentAppId, username)
AgentIdentity.AppOnly(agentAppId) AgentIdentity.appOnly(agentAppId)
WithForceRefresh(true) .forceRefresh(true)
WithFmiPathForClientAssertion(agentAppId) on Leg 2 ClientCredentialParameters.credentialFmiPath(agentAppId) on Leg 2
PropagateOuterRequestParameters propagateToClientCredentialParams / propagateToUserFicParams / propagateToSilentParams
AgentCcaCache dictionary on CCA agentCcaCache ConcurrentHashMap on CCA
Separate AppTokenCacheInternal / UserTokenCacheInternal Flat cache + extCacheKeyHash isolation (Java's existing architecture)

Known differences from .NET

Aspect .NET Java Rationale
Correlation ID propagation WithCorrelationId() propagated to all inner calls Each inner call gets an auto-generated correlation ID Java parameter builders don't have per-request correlationId; adding it is a cross-cutting API change
Cache architecture Physically separate app and user token caches Flat cache with extCacheKeyHash + homeAccountId isolation Java's existing cache design; credential_fmi_path hash provides equivalent isolation for Leg 2
WithSendX5C Available on agent parameter builder Not available Can be added if needed
WithAttributes (xms_attr claims) Available on client credentials builder Not available Separate FMI feature, not specific to agent identity

@Avery-Dunn Avery-Dunn force-pushed the avdunn/fic-support branch from c94ca99 to 07bdbb7 Compare May 6, 2026 21:34
@Avery-Dunn Avery-Dunn force-pushed the avdunn/agent-id-support branch from 0d78e28 to 9752d99 Compare May 6, 2026 22:11
@Avery-Dunn Avery-Dunn changed the title First draft agent ID support Add composite acquireTokenForAgent API for agent identity scenarios May 7, 2026
@Avery-Dunn Avery-Dunn marked this pull request as ready for review May 7, 2026 22:05
@Avery-Dunn Avery-Dunn requested a review from a team as a code owner May 7, 2026 22:05
@Avery-Dunn Avery-Dunn changed the title Add composite acquireTokenForAgent API for agent identity scenarios [Proposal] Add agentic ID support May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant